import subprocess
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
                             QPushButton, QFileDialog, QListWidget, QMessageBox, QSplitter, QScrollArea, QTreeWidget, QTreeWidgetItem, QTabWidget, QHeaderView, QToolButton, QGridLayout, QGroupBox, QComboBox, QDialog, QDialogButtonBox, QProgressDialog)
from PyQt5.QtCore import Qt, QUrl, pyqtSignal
import os, shutil, json
from config.config import DEFAULT_PROJECT_ROOT, PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT
from PyQt5.QtGui import QPixmap, QDesktopServices
from collections import defaultdict
import traceback

from gui.video_progress_panel import VideoProgressPanel


# Add helper function for loading the first image from a folder.
def load_image_for_preview(folder):
    if os.path.isdir(folder):
        for f in os.listdir(folder):
            if f.lower().endswith(('.png','.jpg','.jpeg','.bmp','.gif')):
                return os.path.join(folder, f)
    return None

class ProjectManagerPanel(QWidget):
    projectSelected = pyqtSignal(str)  # 新增：当项目选中时发射当前项目根路径

    def __init__(self, parent=None):
        super().__init__(parent)
        self.root_dir = DEFAULT_PROJECT_ROOT  # Use config value
        self.current_project = ""
        self.current_ids_path = ""  # 新增加，用于存储IDs子文件夹路径
        self.current_videos_path = ""  # 新增：存储视频目录路径（位于选中项目下 Videos 子文件夹）
        self.last_rgb_folder = ""
        self.last_ti_folder = ""
        self.rgb_images = []
        self.rgb_index = 0
        self.ti_images = []
        self.ti_index = 0
        self.current_id_path = ""  # 当前选中的ID路径
        self._init_ui()
        
    def _init_ui(self):
        main_layout = QVBoxLayout(self)
        
        # 根目录选择
        root_layout = QHBoxLayout()
        self.root_edit = QLineEdit()
        self.root_edit.setText(DEFAULT_PROJECT_ROOT)
        btn_select_root = QPushButton("选择根目录")
        btn_select_root.clicked.connect(self.select_root)
        root_layout.addWidget(QLabel("根目录:"))
        root_layout.addWidget(self.root_edit)
        root_layout.addWidget(btn_select_root)
        main_layout.addLayout(root_layout)
        
        # 主内容区域
        splitter = QSplitter(Qt.Horizontal)
        
        # 左侧包含项目和ID的选项卡
        left_panel = QWidget()
        left_layout = QVBoxLayout(left_panel)
        
        # 创建选项卡，一个显示项目，一个显示ID
        self.list_tabs = QTabWidget()
        
        # 项目选项卡
        project_tab = QWidget()
        project_layout = QVBoxLayout(project_tab)
        
        self.project_list = QListWidget()
        self.project_list.itemClicked.connect(self.on_project_selected)
        project_layout.addWidget(QLabel("项目列表:"))
        project_layout.addWidget(self.project_list)
        
        # 项目操作按钮
        proj_crud_layout = QHBoxLayout()
        self.new_proj_edit = QLineEdit()
        self.new_proj_edit.setPlaceholderText("项目名称")
        btn_create_proj = QPushButton("新建")
        btn_create_proj.clicked.connect(self.create_project)
        btn_delete_proj = QPushButton("删除")
        btn_delete_proj.clicked.connect(self.delete_project)
        proj_crud_layout.addWidget(self.new_proj_edit)
        proj_crud_layout.addWidget(btn_create_proj)
        proj_crud_layout.addWidget(btn_delete_proj)
        project_layout.addLayout(proj_crud_layout)
        
        # ID选项卡
        id_tab = QWidget()
        id_layout = QVBoxLayout(id_tab)
        
        self.id_list = QListWidget()
        self.id_list.itemClicked.connect(self.on_id_selected)
        id_layout.addWidget(QLabel("ID列表:"))
        id_layout.addWidget(self.id_list)
        
        # ID操作按钮
        id_crud_layout = QHBoxLayout()
        self.new_id_edit = QLineEdit()
        self.new_id_edit.setPlaceholderText("ID名称")
        btn_create_id = QPushButton("新建")
        btn_create_id.clicked.connect(self.create_id)
        btn_delete_id = QPushButton("删除")
        btn_delete_id.clicked.connect(self.delete_id)
        btn_reorganize_id = QPushButton("整理ID")  # 添加整理ID按钮
        btn_reorganize_id.clicked.connect(self.reorganize_ids)
        id_crud_layout.addWidget(self.new_id_edit)
        id_crud_layout.addWidget(btn_create_id)
        id_crud_layout.addWidget(btn_delete_id)
        id_crud_layout.addWidget(btn_reorganize_id)  # 添加到布局中
        
        # 添加ID批量管理按钮区域
        id_batch_layout = QHBoxLayout()
        btn_delete_empty_ids = QPushButton("删除空ID")
        btn_delete_empty_ids.clicked.connect(self.delete_empty_ids)
        btn_delete_empty_ids.setToolTip("删除RGB和TI文件夹下都没有图片的ID")
        id_batch_layout.addWidget(btn_delete_empty_ids)
        
        # 将两个布局添加到ID标签页
        id_layout.addLayout(id_crud_layout)
        id_layout.addLayout(id_batch_layout)
        
        # 将选项卡添加到选项卡控件中
        self.list_tabs.addTab(project_tab, "项目")
        self.list_tabs.addTab(id_tab, "ID")
        self.list_tabs.currentChanged.connect(self.on_tab_changed)
        
        left_layout.addWidget(self.list_tabs)
        self.current_proj_label = QLabel("当前项目: None")
        left_layout.addWidget(self.current_proj_label)
        
        # 右侧管理选项卡
        management_tabs = QTabWidget()
        
        # ID管理标签页（保持不变）
        id_panel = self._create_id_panel()
        management_tabs.addTab(id_panel, "ID管理")

        btn_archive_projects = QPushButton("归档多项目")
        btn_archive_projects.setToolTip("将多个项目的ID合并到单一数据集")
        btn_archive_projects.clicked.connect(self.archive_projects_to_dataset)
        id_batch_layout.addWidget(btn_archive_projects)
        
        # 视频进度标签页（关键修改部分）
        self.video_progress_panel = VideoProgressPanel()
        management_tabs.addTab(self.video_progress_panel, "视频进度")
        
        splitter.addWidget(left_panel)
        splitter.addWidget(management_tabs)
        splitter.setSizes([200, 400])
        
        main_layout.addWidget(splitter)
        self.refresh_projects()
   
    def archive_projects_to_dataset(self):
        """归档多个项目的ID到单一数据集，并顺序编号"""
        if not self.root_dir:
            QMessageBox.warning(self, "警告", "请先设置项目根目录")
            return
            
        # 创建对话框选择要归档的项目
        dialog = QDialog(self)
        dialog.setWindowTitle("选择要归档的项目")
        dialog.setMinimumWidth(500)
        
        layout = QVBoxLayout(dialog)
        
        # 项目列表（多选）
        layout.addWidget(QLabel("选择要归档的项目:"))
        project_list = QListWidget()
        project_list.setSelectionMode(QListWidget.MultiSelection)
        
        # 获取所有项目
        projects = [d for d in os.listdir(self.root_dir)
                    if os.path.isdir(os.path.join(self.root_dir, d))]
        project_list.addItems(projects)
        layout.addWidget(project_list)
        
        # 目标文件夹选择
        dest_layout = QHBoxLayout()
        dest_layout.addWidget(QLabel("目标数据集文件夹:"))
        dest_edit = QLineEdit()
        dest_btn = QPushButton("浏览...")
        dest_btn.clicked.connect(lambda: dest_edit.setText(
            QFileDialog.getExistingDirectory(dialog, "选择目标数据集文件夹")))
        dest_layout.addWidget(dest_edit)
        dest_layout.addWidget(dest_btn)
        layout.addLayout(dest_layout)
        
        # 按钮区域
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(dialog.accept)
        button_box.rejected.connect(dialog.reject)
        layout.addWidget(button_box)
        
        if dialog.exec_() != QDialog.Accepted:
            return
        
        # 获取选中的项目
        selected_projects = [item.text() for item in project_list.selectedItems()]
        if not selected_projects:
            QMessageBox.warning(self, "警告", "请至少选择一个项目")
            return
        
        # 获取目标文件夹
        dest_folder = dest_edit.text().strip()
        if not dest_folder:
            QMessageBox.warning(self, "警告", "请指定目标数据集文件夹")
            return
        
        # 创建目标数据集结构
        try:
            if not os.path.exists(dest_folder):
                os.makedirs(dest_folder)
            
            # 创建IDs文件夹
            ids_folder = os.path.join(dest_folder, "IDs")
            if not os.path.exists(ids_folder):
                os.makedirs(ids_folder)
        except Exception as e:
            QMessageBox.critical(self, "错误", f"创建目标文件夹失败: {str(e)}")
            return
        
        # 收集所有项目中的ID
        all_ids = []
        for project in selected_projects:
            project_path = os.path.join(self.root_dir, project)
            project_ids_path = os.path.join(project_path, "IDs")
            
            if os.path.exists(project_ids_path) and os.path.isdir(project_ids_path):
                for id_name in os.listdir(project_ids_path):
                    id_path = os.path.join(project_ids_path, id_name)
                    if os.path.isdir(id_path):
                        all_ids.append((project, id_name, id_path))
        
        if not all_ids:
            QMessageBox.warning(self, "警告", "选择的项目中没有找到任何ID")
            return
        
        # 显示进度对话框
        progress = QProgressDialog("归档ID中...", "取消", 0, len(all_ids), self)
        progress.setWindowTitle("归档进度")
        progress.setWindowModality(Qt.WindowModal)
        progress.show()
        
        # 开始复制和重命名ID
        new_id_count = 1
        for idx, (project, id_name, id_path) in enumerate(all_ids):
            progress.setValue(idx)
            progress.setLabelText(f"正在归档: {project} - ID {id_name}")
            if progress.wasCanceled():
                break
                
            # 创建新ID文件夹
            new_id_folder = os.path.join(ids_folder, str(new_id_count))
            new_rgb_folder = os.path.join(new_id_folder, "RGB")
            new_ti_folder = os.path.join(new_id_folder, "TI")
            
            try:
                os.makedirs(new_id_folder, exist_ok=True)
                os.makedirs(new_rgb_folder, exist_ok=True)
                os.makedirs(new_ti_folder, exist_ok=True)
                
                # 复制RGB图片
                rgb_folder = os.path.join(id_path, "RGB")
                if os.path.exists(rgb_folder):
                    for f in os.listdir(rgb_folder):
                        if f.lower().endswith(('.jpg', '.png', '.jpeg', '.bmp', '.gif')):
                            src_file = os.path.join(rgb_folder, f)
                            dst_file = os.path.join(new_rgb_folder, f)
                            shutil.copy2(src_file, dst_file)
                            
                    # 复制RGB元数据文件
                    metadata_file = os.path.join(rgb_folder, "screenshots_metadata.json")
                    if os.path.exists(metadata_file):
                        shutil.copy2(metadata_file, os.path.join(new_rgb_folder, "screenshots_metadata.json"))
                
                # 复制TI图片
                ti_folder = os.path.join(id_path, "TI")
                if os.path.exists(ti_folder):
                    for f in os.listdir(ti_folder):
                        if f.lower().endswith(('.jpg', '.png', '.jpeg', '.bmp', '.gif')):
                            src_file = os.path.join(ti_folder, f)
                            dst_file = os.path.join(new_ti_folder, f)
                            shutil.copy2(src_file, dst_file)
                            
                    # 复制TI元数据文件
                    metadata_file = os.path.join(ti_folder, "screenshots_metadata.json")
                    if os.path.exists(metadata_file):
                        shutil.copy2(metadata_file, os.path.join(new_ti_folder, "screenshots_metadata.json"))
                
                # 复制描述文件
                desc_file = os.path.join(id_path, "descript.json")
                if os.path.exists(desc_file):
                    shutil.copy2(desc_file, os.path.join(new_id_folder, "descript.json"))
                
                # 成功后增加ID计数
                new_id_count += 1
                
            except Exception as e:
                QMessageBox.critical(self, "错误", f"归档 {project} 的 ID {id_name} 时出错: {str(e)}")
                import traceback
                traceback.print_exc()
        
        progress.setValue(len(all_ids))
        QMessageBox.information(self, "完成", f"归档完成！共处理了 {new_id_count-1} 个ID\n保存在: {dest_folder}")
    
    def _create_id_panel(self):
        """创建ID管理面板（保持原有逻辑）"""
        id_panel = QWidget()
        id_layout = QVBoxLayout(id_panel)
        
        # 添加ID描述区域 - 设置为固定大小，不可调整的滚动区域
        self.description_group = QGroupBox("ID特征描述")
        self.description_group.setFixedHeight(500)  # 固定高度
        
        self.description_group.setStyleSheet(
            "QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 10px; }"
        )
        
        
        # 使用QScrollArea确保大量特征可滚动查看
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        
        scroll_content = QWidget()
        desc_layout = QGridLayout(scroll_content)
        desc_layout.setColumnStretch(1, 1)
        desc_layout.setColumnStretch(3, 1)
        
        # 基本属性（第1-2行）
        row = 0
        desc_layout.addWidget(QLabel("性别:"), row, 0)
        self.gender_combo = QComboBox()
        self.gender_combo.addItems(["未知", "男", "女"])
        desc_layout.addWidget(self.gender_combo, row, 1)

        desc_layout.addWidget(QLabel("年龄段:"), row, 2)
        self.age_group_combo = QComboBox()
        self.age_group_combo.addItems(["未知", "儿童", "青少年", "青年", "中年", "老年"])
        desc_layout.addWidget(self.age_group_combo, row, 3)

        # 移到下一行
        row += 1
        desc_layout.addWidget(QLabel("身高:"), row, 0)
        self.height_combo = QComboBox()
        self.height_combo.addItems(["未知", "矮", "中等", "高"])
        desc_layout.addWidget(self.height_combo, row, 1)

        desc_layout.addWidget(QLabel("体型:"), row, 2)
        self.build_combo = QComboBox()
        self.build_combo.addItems(["未知", "瘦", "中等", "胖", "健壮/运动型"])
        desc_layout.addWidget(self.build_combo, row, 3)
        
        # 头发特征（第3行）
        row += 1
        desc_layout.addWidget(QLabel("头发长度:"), row, 0)
        self.hair_length_combo = QComboBox()
        self.hair_length_combo.addItems(["未知", "短发", "中长发", "长发", "秃顶/光头"])
        desc_layout.addWidget(self.hair_length_combo, row, 1)
        
        desc_layout.addWidget(QLabel("头发颜色:"), row, 2)
        self.hair_color_combo = QComboBox()
        self.hair_color_combo.addItems(["未知", "黑色", "棕色", "金色", "灰白", "染色"])
        desc_layout.addWidget(self.hair_color_combo, row, 3)
        
        # 上衣特征（第4-5行）
        row += 1
        desc_layout.addWidget(QLabel("上衣颜色:"), row, 0)
        self.upper_color_combo = QComboBox()
        self.upper_color_combo.addItems(["未知", "红色", "橙色", "黄色", "绿色", "蓝色", "紫色", "粉色", "白色", "灰色", "黑色", "棕色", "多色"])
        desc_layout.addWidget(self.upper_color_combo, row, 1)
        
        desc_layout.addWidget(QLabel("上衣类型:"), row, 2)
        self.upper_type_combo = QComboBox()
        self.upper_type_combo.addItems(["未知", "T恤", "衬衫", "毛衣", "卫衣", "外套", "夹克", "西装", "背心", "连衣裙", "风衣", "制服", "运动服"])
        desc_layout.addWidget(self.upper_type_combo, row, 3)
        
        row += 1
        desc_layout.addWidget(QLabel("袖长:"), row, 0)
        self.sleeve_length_combo = QComboBox()
        self.sleeve_length_combo.addItems(["未知", "无袖", "短袖", "中袖", "长袖"])
        desc_layout.addWidget(self.sleeve_length_combo, row, 1)
        
        desc_layout.addWidget(QLabel("上衣纹理:"), row, 2)
        self.upper_texture_combo = QComboBox()
        self.upper_texture_combo.addItems(["未知", "纯色", "条纹", "格子", "花纹", "图案", "其他"])
        desc_layout.addWidget(self.upper_texture_combo, row, 3)
        
        # 下装特征（第6-7行）
        row += 1
        desc_layout.addWidget(QLabel("下装颜色:"), row, 0)
        self.lower_color_combo = QComboBox()
        self.lower_color_combo.addItems(["未知", "红色", "橙色", "黄色", "绿色", "蓝色", "紫色", "粉色", "白色", "灰色", "黑色", "棕色", "多色"])
        desc_layout.addWidget(self.lower_color_combo, row, 1)
        
        desc_layout.addWidget(QLabel("下装类型:"), row, 2)
        self.lower_type_combo = QComboBox()
        self.lower_type_combo.addItems(["未知", "牛仔裤", "休闲裤", "运动裤", "西装裤", "短裤", "裙子", "连衣裙", "背带裤", "紧身裤/打底裤"])
        desc_layout.addWidget(self.lower_type_combo, row, 3)
        
        row += 1
        desc_layout.addWidget(QLabel("下装长度:"), row, 0)
        self.lower_length_combo = QComboBox()
        self.lower_length_combo.addItems(["未知", "长", "七分/九分", "短"])
        desc_layout.addWidget(self.lower_length_combo, row, 1)
        
        desc_layout.addWidget(QLabel("下装纹理:"), row, 2)
        self.lower_texture_combo = QComboBox()
        self.lower_texture_combo.addItems(["未知", "纯色", "条纹", "格子", "花纹", "图案", "其他"])
        desc_layout.addWidget(self.lower_texture_combo, row, 3)
        
        # 鞋子特征（第8行）
        row += 1
        desc_layout.addWidget(QLabel("鞋子类型:"), row, 0)
        self.shoes_type_combo = QComboBox()
        self.shoes_type_combo.addItems(["未知", "运动鞋", "皮鞋", "靴子", "凉鞋", "高跟鞋", "平底鞋", "拖鞋"])
        desc_layout.addWidget(self.shoes_type_combo, row, 1)
        
        desc_layout.addWidget(QLabel("鞋子颜色:"), row, 2)
        self.shoes_color_combo = QComboBox()
        self.shoes_color_combo.addItems(["未知", "白色", "黑色", "红色", "蓝色", "棕色", "灰色", "多色"])
        desc_layout.addWidget(self.shoes_color_combo, row, 3)
        
        # 配饰特征（第9-10行）
        row += 1
        desc_layout.addWidget(QLabel("背包:"), row, 0)
        self.backpack_combo = QComboBox()
        self.backpack_combo.addItems(["未知", "无", "双肩包", "单肩包", "手提包", "行李箱", "公文包", "其他"])
        desc_layout.addWidget(self.backpack_combo, row, 1)
        
        desc_layout.addWidget(QLabel("头部装饰:"), row, 2)
        self.headwear_combo = QComboBox()
        self.headwear_combo.addItems(["未知", "无", "棒球帽", "渔夫帽", "贝雷帽", "头巾", "冬帽", "安全帽", "其他"])
        desc_layout.addWidget(self.headwear_combo, row, 3)
        
        row += 1
        desc_layout.addWidget(QLabel("眼镜:"), row, 0)
        self.eyewear_combo = QComboBox()
        self.eyewear_combo.addItems(["未知", "无", "眼镜", "太阳镜", "其他"])
        desc_layout.addWidget(self.eyewear_combo, row, 1)
        
        desc_layout.addWidget(QLabel("其他配饰:"), row, 2)
        self.other_accessory_combo = QComboBox()
        self.other_accessory_combo.addItems(["未知", "无", "手表", "围巾/披肩", "领带", "手持物品", "多种配饰"])
        desc_layout.addWidget(self.other_accessory_combo, row, 3)
        
        # 特殊特征（第11行）
        row += 1
        desc_layout.addWidget(QLabel("面部特征:"), row, 0)
        self.facial_feature_combo = QComboBox()
        self.facial_feature_combo.addItems(["未知", "无", "胡须", "眉毛特殊", "面部装饰", "其他"])
        desc_layout.addWidget(self.facial_feature_combo, row, 1)
        
        desc_layout.addWidget(QLabel("动作特点:"), row, 2)
        self.action_feature_combo = QComboBox()
        self.action_feature_combo.addItems(["未知", "无", "行走", "跑步", "坐着", "站立", "骑车", "其他"])
        desc_layout.addWidget(self.action_feature_combo, row, 3)
        
        # 备注区（第12行）
        row += 1
        desc_layout.addWidget(QLabel("备注:"), row, 0)
        self.remarks_edit = QLineEdit()
        desc_layout.addWidget(self.remarks_edit, row, 1, 1, 3)
        
        # 保存描述按钮（第13行）
        row += 1
        self.save_desc_btn = QPushButton("保存描述")
        self.save_desc_btn.clicked.connect(self.save_description)
        desc_layout.addWidget(self.save_desc_btn, row, 0, 1, 4)
        
        scroll_area.setWidget(scroll_content)
        
        description_layout = QVBoxLayout()
        description_layout.addWidget(scroll_area)
        self.description_group.setLayout(description_layout)
        id_layout.addWidget(self.description_group)
        
        # 启用/禁用描述区域
        self.description_group.setEnabled(False)
        
        # 新增预览区域
        self.preview_label = QLabel("预览:")
        self.preview_label.setWordWrap(True)
        id_layout.addWidget(self.preview_label)
        # Setup RGB preview with small arrow buttons and count label
        self.rgb_preview = QLabel("RGB预览")
        self.rgb_preview.setAlignment(Qt.AlignCenter)
        self.rgb_preview.setFixedSize(PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT)
        self.rgb_preview.setStyleSheet("border: 2px inset gray;")
        self.rgb_preview.setToolTip("双击打开当前ID文件夹")
        self.rgb_preview.installEventFilter(self)
        self.rgb_prev_btn = QToolButton()
        self.rgb_prev_btn.setArrowType(Qt.LeftArrow)
        self.rgb_prev_btn.setFixedSize(24, 24)
        self.rgb_prev_btn.clicked.connect(self.show_prev_rgb)
        self.rgb_next_btn = QToolButton()
        self.rgb_next_btn.setArrowType(Qt.RightArrow)
        self.rgb_next_btn.setFixedSize(24, 24)
        self.rgb_next_btn.clicked.connect(self.show_next_rgb)
        self.rgb_count_label = QLabel("")
        self.rgb_count_label.setAlignment(Qt.AlignCenter)
        rgb_layout = QGridLayout()
        rgb_layout.addWidget(self.rgb_prev_btn, 1, 0)
        rgb_layout.addWidget(self.rgb_preview, 1, 1)
        rgb_layout.addWidget(self.rgb_next_btn, 1, 2)
        rgb_layout.addWidget(self.rgb_count_label, 2, 1)
        # Setup TI preview with small arrow buttons and count label
        self.ti_preview = QLabel("TI预览")
        self.ti_preview.setAlignment(Qt.AlignCenter)
        self.ti_preview.setFixedSize(PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT)
        self.ti_preview.setStyleSheet("border: 2px inset gray;")
        self.ti_preview.setToolTip("双击打开TI文件夹")
        self.ti_preview.installEventFilter(self)
        self.ti_prev_btn = QToolButton()
        self.ti_prev_btn.setArrowType(Qt.LeftArrow)
        self.ti_prev_btn.setFixedSize(24, 24)
        self.ti_prev_btn.clicked.connect(self.show_prev_ti)
        self.ti_next_btn = QToolButton()
        self.ti_next_btn.setArrowType(Qt.RightArrow)
        self.ti_next_btn.setFixedSize(24, 24)
        self.ti_next_btn.clicked.connect(self.show_next_ti)
        self.ti_count_label = QLabel("")
        self.ti_count_label.setAlignment(Qt.AlignCenter)
        ti_layout = QGridLayout()
        ti_layout.addWidget(self.ti_prev_btn, 1, 0)
        ti_layout.addWidget(self.ti_preview, 1, 1)
        ti_layout.addWidget(self.ti_next_btn, 1, 2)
        ti_layout.addWidget(self.ti_count_label, 2, 1)
        # Arrange both RGB and TI preview layouts on one row with spacing between them
        previews_layout = QHBoxLayout()
        previews_layout.addLayout(rgb_layout)
        previews_layout.addSpacing(20)  # Added spacing between previews
        previews_layout.addLayout(ti_layout)
        id_layout.addLayout(previews_layout)
        # 移除打开文件夹的按钮，不再需要
        return id_panel
    
    # 新增：处理树形控件点击
    def on_tree_item_clicked(self, item, column):
        # 检查是否为项目(顶级项)
        if item.parent() is None:
            # 这是项目
            proj_name = item.text(0)
            self.current_project = os.path.join(self.root_dir, proj_name)
            self.current_ids_path = os.path.join(self.current_project, "IDs")
            self.current_videos_path = os.path.join(self.current_project, "Videos")
            self.current_proj_label.setText(f"当前项目: {proj_name}")
            self.video_progress_panel.set_project(self.current_project)
            self.projectSelected.emit(self.current_project)
            # 清除预览和描述
            self.preview_label.setText("预览:")
            self.reset_description_fields()
            self.description_group.setEnabled(False)
        else:
            # 这是ID
            id_name = item.text(0)
            proj_name = item.parent().text(0)
            self.current_project = os.path.join(self.root_dir, proj_name)
            self.current_ids_path = os.path.join(self.current_project, "IDs")
            id_path = os.path.join(self.current_ids_path, id_name)
            self.preview_id(id_name, id_path)
    
    # 修改预览ID的方法以接受ID路径参数
    def preview_id(self, id_name, id_path):
        self.current_id_path = id_path  # Store for opening folder on double-click
        rgb_folder = os.path.join(id_path, "RGB")
        ti_folder = os.path.join(id_path, "TI")
        self.last_rgb_folder = rgb_folder
        self.last_ti_folder = ti_folder
        
        # 加载ID描述文件
        self.load_id_description(id_path)
        
        # 启用描述编辑区
        self.description_group.setEnabled(True)
        
        # Update RGB preview: load all image paths and show the first one.
        if os.path.isdir(rgb_folder):
            self.rgb_images = sorted([os.path.join(rgb_folder, f)
                                 for f in os.listdir(rgb_folder)
                                 if f.lower().endswith(('.png','.jpg','.jpeg','.bmp','.gif'))])
        else:
            self.rgb_images = []
        self.rgb_index = 0
        self.refresh_rgb_preview()
        # Load TI images similarly
        if os.path.isdir(ti_folder):
            self.ti_images = sorted([os.path.join(ti_folder, f)
                for f in os.listdir(ti_folder)
                if f.lower().endswith(('.png','.jpg','.jpeg','.bmp','.gif'))])
        else:
            self.ti_images = []
        self.ti_index = 0
        self.refresh_ti_preview()

    def load_id_description(self, id_path):
        """加载ID的描述文件"""
        desc_file = os.path.join(id_path, "descript.json")
        if os.path.exists(desc_file):
            try:
                with open(desc_file, 'r', encoding='utf-8') as f:
                    desc_data = json.load(f)
                    
                    # 设置基本属性
                    self.gender_combo.setCurrentText(desc_data.get("gender", "未知"))
                    self.age_group_combo.setCurrentText(desc_data.get("age_group", "未知"))
                    self.height_combo.setCurrentText(desc_data.get("height", "未知"))
                    self.build_combo.setCurrentText(desc_data.get("build", "未知"))
                    
                    # 头发特征
                    self.hair_length_combo.setCurrentText(desc_data.get("hair_length", "未知"))
                    self.hair_color_combo.setCurrentText(desc_data.get("hair_color", "未知"))
                    
                    # 上衣特征
                    self.upper_color_combo.setCurrentText(desc_data.get("upper_color", "未知"))
                    self.upper_type_combo.setCurrentText(desc_data.get("upper_type", "未知"))
                    self.sleeve_length_combo.setCurrentText(desc_data.get("sleeve_length", "未知"))
                    self.upper_texture_combo.setCurrentText(desc_data.get("upper_texture", "未知"))
                    
                    # 下装特征
                    self.lower_color_combo.setCurrentText(desc_data.get("lower_color", "未知"))
                    self.lower_type_combo.setCurrentText(desc_data.get("lower_type", "未知"))
                    self.lower_length_combo.setCurrentText(desc_data.get("lower_length", "未知"))
                    self.lower_texture_combo.setCurrentText(desc_data.get("lower_texture", "未知"))
                    
                    # 鞋子特征
                    self.shoes_type_combo.setCurrentText(desc_data.get("shoes_type", "未知"))
                    self.shoes_color_combo.setCurrentText(desc_data.get("shoes_color", "未知"))
                    
                    # 配饰特征
                    self.backpack_combo.setCurrentText(desc_data.get("backpack", "未知"))
                    self.headwear_combo.setCurrentText(desc_data.get("headwear", "未知"))
                    self.eyewear_combo.setCurrentText(desc_data.get("eyewear", "未知"))
                    self.other_accessory_combo.setCurrentText(desc_data.get("other_accessory", "未知"))
                    
                    # 特殊特征
                    self.facial_feature_combo.setCurrentText(desc_data.get("facial_feature", "未知"))
                    self.action_feature_combo.setCurrentText(desc_data.get("action_feature", "未知"))
                    
                    # 备注
                    self.remarks_edit.setText(desc_data.get("remarks", ""))
                    
            except Exception as e:
                QMessageBox.warning(self, "警告", f"加载描述文件出错: {str(e)}")
                traceback.print_exc()
                self.reset_description_fields()
        else:
            self.reset_description_fields()

    def save_description(self):
        """保存当前ID的特征描述"""
        if not self.current_id_path:
            QMessageBox.warning(self, "警告", "请先选择一个ID")
            return
        
        # 收集描述数据
        desc_data = {
            # 基本属性
            "gender": self.gender_combo.currentText(),
            "age_group": self.age_group_combo.currentText(),
            "height": self.height_combo.currentText(),
            "build": self.build_combo.currentText(),
            
            # 头发特征
            "hair_length": self.hair_length_combo.currentText(),
            "hair_color": self.hair_color_combo.currentText(),
            
            # 上衣特征
            "upper_color": self.upper_color_combo.currentText(),
            "upper_type": self.upper_type_combo.currentText(),
            "sleeve_length": self.sleeve_length_combo.currentText(),
            "upper_texture": self.upper_texture_combo.currentText(),
            
            # 下装特征
            "lower_color": self.lower_color_combo.currentText(),
            "lower_type": self.lower_type_combo.currentText(),
            "lower_length": self.lower_length_combo.currentText(),
            "lower_texture": self.lower_texture_combo.currentText(),
            
            # 鞋子特征
            "shoes_type": self.shoes_type_combo.currentText(),
            "shoes_color": self.shoes_color_combo.currentText(),
            
            # 配饰特征
            "backpack": self.backpack_combo.currentText(),
            "headwear": self.headwear_combo.currentText(),
            "eyewear": self.eyewear_combo.currentText(),
            "other_accessory": self.other_accessory_combo.currentText(),
            
            # 特殊特征
            "facial_feature": self.facial_feature_combo.currentText(),
            "action_feature": self.action_feature_combo.currentText(),
            
            # 备注
            "remarks": self.remarks_edit.text()
        }
        
        # 保存到JSON文件
        desc_file = os.path.join(self.current_id_path, "descript.json")
        try:
            with open(desc_file, 'w', encoding='utf-8') as f:
                json.dump(desc_data, f, ensure_ascii=False, indent=2)
            QMessageBox.information(self, "成功", "ID描述已保存")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"保存描述文件出错: {e}")
            traceback.print_exc()

    def reset_description_fields(self):
        """重置描述字段为默认值"""
        # 基本属性
        self.gender_combo.setCurrentText("未知")
        self.age_group_combo.setCurrentText("未知")
        self.height_combo.setCurrentText("未知")
        self.build_combo.setCurrentText("未知")
        
        # 头发特征
        self.hair_length_combo.setCurrentText("未知")
        self.hair_color_combo.setCurrentText("未知")
        
        # 上衣特征
        self.upper_color_combo.setCurrentText("未知")
        self.upper_type_combo.setCurrentText("未知")
        self.sleeve_length_combo.setCurrentText("未知")
        self.upper_texture_combo.setCurrentText("未知")
        
        # 下装特征
        self.lower_color_combo.setCurrentText("未知")
        self.lower_type_combo.setCurrentText("未知")
        self.lower_length_combo.setCurrentText("未知")
        self.lower_texture_combo.setCurrentText("未知")
        
        # 鞋子特征
        self.shoes_type_combo.setCurrentText("未知")
        self.shoes_color_combo.setCurrentText("未知")
        
        # 配饰特征
        self.backpack_combo.setCurrentText("未知")
        self.headwear_combo.setCurrentText("未知")
        self.eyewear_combo.setCurrentText("未知")
        self.other_accessory_combo.setCurrentText("未知")
        
        # 特殊特征
        self.facial_feature_combo.setCurrentText("未知")
        self.action_feature_combo.setCurrentText("未知")
        
        # 备注
        self.remarks_edit.clear()

    def refresh_rgb_preview(self):
        if self.rgb_images:
            current_image = self.rgb_images[self.rgb_index]
            pixmap = QPixmap(current_image)
            self.rgb_preview.setPixmap(
                pixmap.scaled(self.rgb_preview.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
            self.rgb_preview.setText("")
            self.rgb_count_label.setText(f"{self.rgb_index+1}/{len(self.rgb_images)}")
        else:
            self.rgb_preview.setText("No Image")
            self.rgb_preview.clear()
            self.rgb_count_label.setText("0/0")

    def show_prev_rgb(self):
        if self.rgb_images:
            self.rgb_index = (self.rgb_index - 1) % len(self.rgb_images)
            self.refresh_rgb_preview()

    def show_next_rgb(self):
        if self.rgb_images:
            self.rgb_index = (self.rgb_index + 1) % len(self.rgb_images)
            self.refresh_rgb_preview()

    def refresh_ti_preview(self):
        if self.ti_images:
            current_image = self.ti_images[self.ti_index]
            pixmap = QPixmap(current_image)
            self.ti_preview.setPixmap(
                pixmap.scaled(self.ti_preview.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
            self.ti_preview.setText("")
            self.ti_count_label.setText(f"{self.ti_index+1}/{len(self.ti_images)}")
        else:
            self.ti_preview.setText("No Image")
            self.ti_preview.clear()
            self.ti_count_label.setText("0/0")

    def show_prev_ti(self):
        if self.ti_images:
            self.ti_index = (self.ti_index - 1) % len(self.ti_images)
            self.refresh_ti_preview()

    def show_next_ti(self):
        if self.ti_images:
            self.ti_index = (self.ti_index + 1) % len(self.ti_images)
            self.refresh_ti_preview()

    def eventFilter(self, obj, event):
        if event.type() == event.MouseButtonDblClick:
            if (obj == self.rgb_preview) or (obj == self.ti_preview):
                # Double-click opens the current ID folder
                if self.current_id_path and os.path.isdir(self.current_id_path):
                    QDesktopServices.openUrl(QUrl.fromLocalFile(self.current_id_path))
                return True
        return super().eventFilter(obj, event)

    def open_rgb_folder(self):
        if self.last_rgb_folder:
            QDesktopServices.openUrl(QUrl.fromLocalFile(self.last_rgb_folder))
            
    def open_ti_folder(self):
        if self.last_ti_folder:
            QDesktopServices.openUrl(QUrl.fromLocalFile(self.last_ti_folder))
            
    def select_root(self):
        folder = QFileDialog.getExistingDirectory(self, "选择项目根目录")
        if folder:
            self.root_dir = folder
            self.root_edit.setText(folder)
            self.refresh_projects()
            
    def refresh_projects(self):
        self.project_list.clear()
        self.id_list.clear()
        self.preview_label.setText("预览:")
        self.current_proj_label.setText("当前项目: None")
        self.description_group.setEnabled(False)
        
        if not self.root_dir:
            return
            
        projects = [d for d in os.listdir(self.root_dir)
                    if os.path.isdir(os.path.join(self.root_dir, d))]
        self.project_list.addItems(projects)
    
    def refresh_ids(self):
        """刷新ID列表"""
        self.id_list.clear()
        self.preview_label.setText("预览:")
        if not self.current_ids_path or not os.path.isdir(self.current_ids_path):
            return
            
        ids = [d for d in os.listdir(self.current_ids_path)
               if os.path.isdir(os.path.join(self.current_ids_path, d))]
        ids_sorted = sorted(ids, key=lambda x: int(x) if x.isdigit() else x)
        self.id_list.addItems(ids_sorted)
    
    def create_project(self):
        """创建新项目"""
        if not self.root_dir:
            QMessageBox.warning(self, "警告", "请先选择项目根目录")
            return
            
        proj_name = self.new_proj_edit.text().strip()
        if not proj_name:
            QMessageBox.warning(self, "警告", "请输入项目名称")
            return
            
        proj_path = os.path.join(self.root_dir, proj_name)
        if os.path.exists(proj_path):
            QMessageBox.warning(self, "警告", "项目已存在")
            return
            
        try:
            os.makedirs(proj_path)
            # 创建 Videos、Frames 和 IDs 子文件夹
            os.makedirs(os.path.join(proj_path, "Videos"))
            os.makedirs(os.path.join(proj_path, "Frames"))
            os.makedirs(os.path.join(proj_path, "IDs"))
        except Exception as e:
            QMessageBox.critical(self, "错误", f"创建项目出错: {e}")
        else:
            self.refresh_projects()
            
    def delete_project(self):
        """删除项目"""
        selected = self.project_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "警告", "请选择项目删除")
            return
            
        proj_name = selected.text()
        proj_path = os.path.join(self.root_dir, proj_name)
        reply = QMessageBox.question(self, "确认", f"删除项目 {proj_name} 吗？", 
                                    QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            try:
                shutil.rmtree(proj_path)
            except Exception as e:
                QMessageBox.critical(self, "错误", f"删除项目出错: {e}")
            else:
                self.refresh_projects()
                # 如果当前项目被删除，重置状态
                if self.current_project == proj_path:
                    self.current_project = ""
                    self.current_ids_path = ""
                    self.current_proj_label.setText("当前项目: None")
    
    def create_id(self):
        """创建新ID"""
        if not self.current_ids_path:
            QMessageBox.warning(self, "警告", "请先选择一个项目")
            return
            
        new_id = self.new_id_edit.text().strip()
        if not new_id:
            existing_ids = [d for d in os.listdir(self.current_ids_path)
                           if os.path.isdir(os.path.join(self.current_ids_path, d)) and d.isdigit()]
            if existing_ids:
                next_num = max(int(x) for x in existing_ids) + 1
            else:
                next_num = 1
            new_id = str(next_num)
            
        id_path = os.path.join(self.current_ids_path, new_id)
        if os.path.exists(id_path):
            QMessageBox.warning(self, "警告", "该ID已存在")
            return
            
        try:
            os.makedirs(os.path.join(id_path, "RGB"))
            os.makedirs(os.path.join(id_path, "TI"))
        except Exception as e:
            QMessageBox.critical(self, "错误", f"创建ID出错: {e}")
        else:
            self.refresh_ids()
            
    def delete_id(self):
        """删除ID"""
        selected = self.id_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "警告", "请选择ID删除")
            return
            
        id_name = selected.text()
        id_path = os.path.join(self.current_ids_path, id_name)
        reply = QMessageBox.question(self, "确认", f"删除ID {id_name} 吗？", 
                                    QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            try:
                shutil.rmtree(id_path)
            except Exception as e:
                QMessageBox.critical(self, "错误", f"删除ID出错: {e}")
            else:
                self.refresh_ids()
    
    def on_tab_changed(self, index):
        """当选项卡改变时处理"""
        if index == 1:  # ID选项卡
            # 刷新ID列表
            self.refresh_ids()
    
    def on_project_selected(self, item):
        """当项目被选中时的处理函数"""
        proj_name = item.text()
        self.current_project = os.path.join(self.root_dir, proj_name)
        self.current_ids_path = os.path.join(self.current_project, "IDs")
        self.current_videos_path = os.path.join(self.current_project, "Videos")
        self.current_proj_label.setText(f"当前项目: {proj_name}")
        self.refresh_ids()
        self.video_progress_panel.set_project(self.current_project)
        self.projectSelected.emit(self.current_project)
        
        # 切换到ID选项卡
        self.list_tabs.setCurrentIndex(1)
        
    def on_id_selected(self, item):
        """当ID被选中时的处理函数"""
        if not self.current_ids_path:
            return
            
        id_name = item.text()
        id_path = os.path.join(self.current_ids_path, id_name)
        self.preview_id(id_path)
    
    def preview_id(self, id_path):
        """预览ID"""
        self.current_id_path = id_path
        rgb_folder = os.path.join(id_path, "RGB")
        ti_folder = os.path.join(id_path, "TI")
        self.last_rgb_folder = rgb_folder
        self.last_ti_folder = ti_folder
        
        # 加载ID描述文件
        self.load_id_description(id_path)
        
        # 启用描述编辑区
        self.description_group.setEnabled(True)
        
        # Update RGB preview: load all image paths and show the first one.
        if os.path.isdir(rgb_folder):
            self.rgb_images = sorted([os.path.join(rgb_folder, f)
                                 for f in os.listdir(rgb_folder)
                                 if f.lower().endswith(('.png','.jpg','.jpeg','.bmp','.gif'))])
        else:
            self.rgb_images = []
        self.rgb_index = 0
        self.refresh_rgb_preview()
        
        # Load TI images similarly
        if os.path.isdir(ti_folder):
            self.ti_images = sorted([os.path.join(ti_folder, f)
                for f in os.listdir(ti_folder)
                if f.lower().endswith(('.png','.jpg','.jpeg','.bmp','.gif'))])
        else:
            self.ti_images = []
        self.ti_index = 0
        self.refresh_ti_preview()
    
    # 新增：当项目选中时的处理函数
    def on_project_selected(self, item):
        proj_name = item.text()
        self.current_project = os.path.join(self.root_dir, proj_name)
        self.current_ids_path = os.path.join(self.current_project, "IDs")
        self.current_videos_path = os.path.join(self.current_project, "Videos")
        self.current_proj_label.setText(f"当前项目: {proj_name}")
        self.refresh_ids()
        self.video_progress_panel.set_project(self.current_project)
        self.projectSelected.emit(self.current_project)
    
    def delete_id(self):
        selected = self.id_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "警告", "请选择ID删除")
            return
        id_name = selected.text()
        id_path = os.path.join(self.current_ids_path, id_name)
        reply = QMessageBox.question(self, "确认", f"删除ID {id_name} 吗？", QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            try:
                shutil.rmtree(id_path)
            except Exception as e:
                QMessageBox.critical(self, "错误", f"删除ID出错: {e}")
            else:
                self.refresh_ids()

    # 添加整理ID的新方法
    def reorganize_ids(self):
        """整理ID序号，使其连续排列（如1,2,4,5 -> 1,2,3,4）"""
        if not self.current_ids_path or not os.path.isdir(self.current_ids_path):
            QMessageBox.warning(self, "警告", "请先选择一个项目")
            return
            
        # 获取所有数字ID并排序
        ids = []
        for id_name in os.listdir(self.current_ids_path):
            id_path = os.path.join(self.current_ids_path, id_name)
            if os.path.isdir(id_path) and id_name.isdigit():
                ids.append(int(id_name))
        
        if not ids:
            QMessageBox.information(self, "提示", "没有发现需要整理的数字ID")
            return
            
        # 排序ID
        ids.sort()
        
        # 检查是否已经是连续的
        if ids[-1] - ids[0] + 1 == len(ids) and ids[0] == 1:
            QMessageBox.information(self, "提示", "ID已经是连续的，无需整理")
            return
            
        # 确认操作
        reply = QMessageBox.question(self, "确认整理ID", 
                                    f"将把ID序列 {', '.join(map(str, ids))} 整理为连续序列 1~{len(ids)}\n此操作不可撤销，确认继续吗？", 
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        
        if reply != QMessageBox.Yes:
            return
            
        # 执行重命名，从最大的ID开始避免命名冲突
        temp_prefix = "_temp_id_"
        rename_map = {}
        
        try:
            # 第一步：将所有ID重命名为临时名称
            for old_id, new_id in zip(ids, range(1, len(ids) + 1)):
                old_path = os.path.join(self.current_ids_path, str(old_id))
                temp_path = os.path.join(self.current_ids_path, f"{temp_prefix}{new_id}")
                os.rename(old_path, temp_path)
                rename_map[temp_path] = os.path.join(self.current_ids_path, str(new_id))
            
            # 第二步：将临时名称改为最终名称
            for temp_path, new_path in rename_map.items():
                os.rename(temp_path, new_path)
                
            QMessageBox.information(self, "成功", f"ID已成功整理为 1~{len(ids)} 的连续序列")
            self.refresh_ids()  # 刷新ID列表
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"整理ID过程中出错: {str(e)}\n可能需要手动检查ID文件夹")
            import traceback
            traceback.print_exc()

    # 添加删除空ID的新方法
    def delete_empty_ids(self):
        """删除没有任何图片的空ID文件夹"""
        if not self.current_ids_path or not os.path.isdir(self.current_ids_path):
            QMessageBox.warning(self, "警告", "请先选择一个项目")
            return
            
        # 获取所有ID
        ids = [d for d in os.listdir(self.current_ids_path)
               if os.path.isdir(os.path.join(self.current_ids_path, d))]
        
        if not ids:
            QMessageBox.information(self, "提示", "没有发现任何ID")
            return
        
        # 查找空ID（RGB和TI子文件夹都没有图片）
        empty_ids = []
        for id_name in ids:
            id_path = os.path.join(self.current_ids_path, id_name)
            rgb_folder = os.path.join(id_path, "RGB")
            ti_folder = os.path.join(id_path, "TI")
            
            # 检查RGB文件夹中是否有图片
            rgb_has_images = False
            if os.path.isdir(rgb_folder):
                for f in os.listdir(rgb_folder):
                    if f.lower().endswith(('.png','.jpg','.jpeg','.bmp','.gif')):
                        rgb_has_images = True
                        break
                        
            # 检查TI文件夹中是否有图片
            ti_has_images = False
            if os.path.isdir(ti_folder):
                for f in os.listdir(ti_folder):
                    if f.lower().endswith(('.png','.jpg','.jpeg','.bmp','.gif')):
                        ti_has_images = True
                        break
            
            # 如果两个文件夹都没有图片，则认为是空ID
            if not rgb_has_images and not ti_has_images:
                empty_ids.append(id_name)
        
        if not empty_ids:
            QMessageBox.information(self, "提示", "没有发现空ID文件夹")
            return
        
        # 确认删除
        reply = QMessageBox.question(self, "确认删除空ID", 
                                    f"发现 {len(empty_ids)} 个空ID文件夹：\n{', '.join(empty_ids)}\n确认删除这些文件夹吗？", 
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        
        if reply != QMessageBox.Yes:
            return
        
        # 执行删除
        deleted_count = 0
        for id_name in empty_ids:
            try:
                shutil.rmtree(os.path.join(self.current_ids_path, id_name))
                deleted_count += 1
            except Exception as e:
                QMessageBox.critical(self, "错误", f"删除ID {id_name} 时出错: {str(e)}")
        
        QMessageBox.information(self, "成功", f"已成功删除 {deleted_count} 个空ID文件夹")
        self.refresh_ids()  # 刷新ID列表



